﻿using System;
using System.Collections.Generic;

namespace JumpingBalls.AppCode
{
    public static class Game
    {
        private static readonly GameContext context = App.Context;

        private static readonly Random random = new Random();

        private static void InitGame()
        {
            context.IsRuning = true;

            context.Score = 0;

            context.BlockLeft = GameContext.GroundLength * GameContext.GroundLength;

            context.NextBall1 = 0;
            context.NextBall2 = 0;
            context.NextBall3 = 0;

            context.Ground = new int[GameContext.GroundLength][];

            for (int i = 0; i < GameContext.GroundLength; i++)
            {
                context.Ground[i] = new int[GameContext.GroundLength];
            }
        }

        public static void StartGame()
        {
            InitGame();

            GenerateNextBalls();

            ApplyNextBallsToGround();

            GenerateNextBalls();
        }

        public static void GenerateNextBalls()
        {
            context.NextBall1 = random.Next(1, GameContext.BallsCount + 1);
            context.NextBall2 = random.Next(1, GameContext.BallsCount + 1);
            context.NextBall3 = random.Next(1, GameContext.BallsCount + 1);
        }

        public static void ApplyNextBallsToGround()
        {
            FillBallInGround(context.Ground, context.NextBall1);
            FillBallInGround(context.Ground, context.NextBall2);
            FillBallInGround(context.Ground, context.NextBall3);
        }

        private static void FillBallInGround(int[][] ground, int ballValue)
        {
            while (context.BlockLeft > 0)
            {
                int row = random.Next(0, GameContext.GroundLength);
                int col = random.Next(0, GameContext.GroundLength);

                if(ground[row][col] == 0)
                {
                    context.BlockLeft--;

                    context.ChangeValue(row, col, ballValue, false);

                    EraseBalls(context.Ground, row, col);

                    if (context.BlockLeft == 0)
                    {
                        context.IsRuning = false;

                        Tops.AddToTop(new TopItem { Score = context.Score, CreateDate = DateTime.Now });
                    }

                    return;
                }
            }
        }

        public static void MoveBall(int from_X, int from_Y, int to_X, int to_Y)
        {
            if (CanReach(from_X, from_Y, to_X, to_Y, CopyGround(context.Ground)))
            {
                int ballValue = context.Ground[from_X][from_Y];

                context.ChangeValue(from_X, from_Y, 0, false);

                context.ChangeValue(to_X, to_Y, ballValue, false);

                context.Score += 5;


                EraseBalls(context.Ground, to_X, to_Y);

                ApplyNextBallsToGround();

                GenerateNextBalls();

            }
        }

        private static bool CanReach(int from_X, int from_Y, int to_X, int to_Y, int[][] ground)
        {
            if(from_X == to_X && from_Y == to_Y)
            {
                return true;
            }

            // Set Visited
            ground[from_X][from_Y] = -1;

            // Visit left ball
            if (from_X - 1 >= 0 && from_Y >= 0 && from_X - 1 < GameContext.GroundLength && from_Y < GameContext.GroundLength)
            {
                if (ground[from_X - 1][from_Y] == 0)
                {
                    if (CanReach(from_X - 1, from_Y, to_X, to_Y, ground))
                    {
                        return true;
                    }
                }
            }

            // Visit top ball
            if (from_X >= 0 && from_Y - 1 >= 0 && from_X < GameContext.GroundLength && from_Y - 1 < GameContext.GroundLength)
            {
                if (ground[from_X][from_Y - 1] == 0)
                {
                    if (CanReach(from_X, from_Y - 1, to_X, to_Y, ground))
                    {
                        return true;
                    }
                }
            }

            // Visit right ball
            if (from_X + 1 >= 0 && from_Y >= 0 && from_X + 1 < GameContext.GroundLength && from_Y < GameContext.GroundLength)
            {
                if (ground[from_X + 1][from_Y] == 0)
                {
                    if (CanReach(from_X + 1, from_Y, to_X, to_Y, ground))
                    {
                        return true;
                    }
                }
            }

            // Visit buttom ball
            if (from_X >= 0 && from_Y + 1 >= 0 && from_X < GameContext.GroundLength && from_Y + 1 < GameContext.GroundLength)
            {
                if (ground[from_X][from_Y + 1] == 0)
                {
                    if (CanReach(from_X, from_Y + 1, to_X, to_Y, ground))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private static void EraseBalls(int[][] ground, int x, int y)
        {
            List<int> erase_X = new List<int>(GameContext.GroundLength * 4);
            List<int> erase_Y = new List<int>(GameContext.GroundLength * 4);

            erase_X.Add(x);
            erase_Y.Add(y);

            // Horizontal line
            List<int> horizontalLine_X = new List<int>(GameContext.GroundLength);
            List<int> horizontalLine_Y = new List<int>(GameContext.GroundLength);

            for (int i = y - 1; i >= 0; i--)
            {
                if (ground[x][i] == ground[x][y])
                {
                    horizontalLine_X.Add(x);
                    horizontalLine_Y.Add(i);
                }
                else
                {
                    break;
                }
            }

            for (int i = y + 1; i < GameContext.GroundLength; i++)
            {
                if (ground[x][i] == ground[x][y])
                {
                    horizontalLine_X.Add(x);
                    horizontalLine_Y.Add(i);
                }
                else
                {
                    break;
                }
            }

            // Vertical line
            List<int> verticalLine_X = new List<int>(GameContext.GroundLength);
            List<int> verticalLine_Y = new List<int>(GameContext.GroundLength);

            for (int i = x - 1; i >= 0; i--)
            {
                if (ground[i][y] == ground[x][y])
                {
                    verticalLine_X.Add(i);
                    verticalLine_Y.Add(y);
                }
                else
                {
                    break;
                }
            }

            for (int i = x + 1; i < GameContext.GroundLength; i++)
            {
                if (ground[i][y] == ground[x][y])
                {
                    verticalLine_X.Add(i);
                    verticalLine_Y.Add(y);
                }
                else
                {
                    break;
                }
            }

            // Left slash
            List<int> leftSlash_X = new List<int>(GameContext.GroundLength);
            List<int> leftSlash_Y = new List<int>(GameContext.GroundLength);

            for (int i = 1; i < 10; i++)
            {
                if (x - i < 0 || y - i < 0)
                {
                    break;
                }

                if (ground[x - i][y - i] == ground[x][y])
                {
                    leftSlash_X.Add(x - i);
                    leftSlash_Y.Add(y - i);
                }
                else
                {
                    break;
                }
            }

            for (int i = 1; i < GameContext.GroundLength; i++)
            {
                if (x + i >= GameContext.GroundLength || y + i >= GameContext.GroundLength)
                {
                    break;
                }

                if (ground[x + i][y + i] == ground[x][y])
                {
                    leftSlash_X.Add(x + i);
                    leftSlash_Y.Add(y + i);
                }
                else
                {
                    break;
                }
            }

            // Right slash
            List<int> rightSlash_X = new List<int>(GameContext.GroundLength);
            List<int> rightSlash_Y = new List<int>(GameContext.GroundLength);

            for (int i = 1; i < 10; i++)
            {
                if (x - i < 0 || y + i < 0 || x - i >= GameContext.GroundLength || y + i >= GameContext.GroundLength)
                {
                    break;
                }

                if (ground[x - i][y + i] == ground[x][y])
                {
                    rightSlash_X.Add(x - i);
                    rightSlash_Y.Add(y + i);
                }
                else
                {
                    break;
                }
            }

            for (int i = 1; i < 10; i++)
            {
                if (x + i < 0 || y - i < 0 || x + i >= GameContext.GroundLength || y - i >= GameContext.GroundLength)
                {
                    break;
                }

                if (ground[x + i][y - i] == ground[x][y])
                {
                    rightSlash_X.Add(x + i);
                    rightSlash_Y.Add(y - i);
                }
                else
                {
                    break;
                }
            }

            // EraseBalls

            if (horizontalLine_X.Count >= 4)
            {
                for (int i = 0; i < horizontalLine_X.Count; i++)
                {
                    erase_X.Add(horizontalLine_X[i]);
                    erase_Y.Add(horizontalLine_Y[i]);
                }
            }

            if (verticalLine_X.Count >= 4)
            {
                for (int i = 0; i < verticalLine_X.Count; i++)
                {
                    erase_X.Add(verticalLine_X[i]);
                    erase_Y.Add(verticalLine_Y[i]);
                }
            }

            if (leftSlash_X.Count >= 4)
            {
                for (int i = 0; i < leftSlash_X.Count; i++)
                {
                    erase_X.Add(leftSlash_X[i]);
                    erase_Y.Add(leftSlash_Y[i]);
                }
            }

            if (rightSlash_X.Count >= 4)
            {
                for (int i = 0; i < rightSlash_X.Count; i++)
                {
                    erase_X.Add(rightSlash_X[i]);
                    erase_Y.Add(rightSlash_Y[i]);
                }
            }

            if (erase_X.Count >= 5)
            {
                for (int i = 0; i < erase_X.Count; i++)
                {
                    EraseBall(erase_X[i], erase_Y[i]);
                }

                context.Score += 10 * CombGain(erase_X.Count - 5);
            }
        }

        private static void EraseBall(int x, int y)
        {
            if (context.Ground[x][y] != 0)
            {
                context.ChangeValue(x, y, 0, true);

                context.Score += 10;

                context.BlockLeft++;
            }
        }

        private static int[][] CopyGround(int[][] ground)
        {
            int[][] newGround = new int[GameContext.GroundLength][];

            for (int i = 0; i < GameContext.GroundLength; i++)
            {
                newGround[i] = new int[GameContext.GroundLength];

                for (int j = 0; j < GameContext.GroundLength; j++)
                {
                    newGround[i][j] = ground[i][j];
                }
            }

            return newGround;
        }

        private static int CombGain(int num)
        {
            int gain = 2 * num;

            while (--num > 0)
            {
                gain += num;
            }

            return gain;
        }
    }
}
